package org.flexthinker.binding.bean
{
    import org.flexthinker.binding.value.support.MockBean;
    import org.hamcrest.assertThat;
    import org.hamcrest.core.throws;
    import org.hamcrest.object.equalTo;
    import org.hamcrest.object.instanceOf;
    import org.hamcrest.object.nullValue;

    public class AutoBindingTest
    {
        private var autoBinding:AutoBinding;
        private var bean:MockBean;
        private var txt:MockTextInput;

        public function AutoBindingTest()
        {
        }

        [Before]
        public function setUp():void
        {
            bean = new MockBean();
            txt = new MockTextInput();
        }


        [Test]
        public function testReadOnce():void
        {
            bean.simpleString = "readOnce";
            txt.text = "beforeBind";
            autoBinding = new AutoBinding(UpdateStrategy.READ_ONCE,
                                            bean, BeanProperty.create("simpleString"),
                                            txt, BeanProperty.create("text"), "readOnceBinding");
            assertThat("Before bind() no values should be updated on source", bean.simpleString, equalTo("readOnce") );
            assertThat("Before bind() no values should be updated on target", txt.text, equalTo("beforeBind") );

            autoBinding.bind();
            assertThat("After bind() target should be changed", txt.text, equalTo("readOnce"));
            assertThat("After bind() source should remain unchanged", bean.simpleString, equalTo("readOnce"));

            bean.simpleString = "readTwice";
            assertThat("After bind() sync should happen only once", txt.text, equalTo("readOnce"));

            txt.text = "NoSync";
            assertThat("After bind() sync should happen only once", bean.simpleString, equalTo("readTwice"));
        }

        [Test]
        public function testReadOnly():void
        {
            bean.simpleString = "readOnly#1";
            txt.text = "beforeBind";
            autoBinding = new AutoBinding(UpdateStrategy.READ,
                                            bean, BeanProperty.create("simpleString"),
                                            txt, BeanProperty.create("text"), "readOnlyBinding");
            assertThat("Before bind() no values should be updated on source", bean.simpleString, equalTo("readOnly#1") );
            assertThat("Before bind() no values should be updated on target", txt.text, equalTo("beforeBind") );

            autoBinding.bind();
            assertThat("After bind() target should be changed", txt.text, equalTo("readOnly#1"));
            assertThat("After bind() source should remain unchanged", bean.simpleString, equalTo("readOnly#1"));

            bean.simpleString = "readTwice";
            assertThat("target should be kept in sync", txt.text, equalTo("readTwice"));

            txt.text = "NoSync";
            assertThat("target changes shouldn't sync with the source", bean.simpleString, equalTo("readTwice"));

            bean.simpleString = "readThird";
            assertThat("target should be kept in sync", txt.text, equalTo("readThird"));

            autoBinding.unbind();
            txt.text = "AAA";
            bean.simpleString = "000";
            assertThat("target shouldn't sync after unbind", txt.text, equalTo("AAA"));
        }

        [Test]
        public function testReadWrite():void
        {
            autoBinding = new AutoBinding(UpdateStrategy.READ_WRITE,
                                            bean, BeanProperty.create("simpleString"),
                                            txt, BeanProperty.create("text"), "readOnlyBinding");
            bean.simpleString = "A";
            txt.text = "txt";

            assertThat("Before bind() no values should be updated on source", bean.simpleString, equalTo("A") );
            assertThat("Before bind() no values should be updated on target", txt.text, equalTo("txt") );

            autoBinding.bind();

            assertThat("after bind() target should be in sync with the source", txt.text, equalTo("A"));

            txt.text = "newText";
            assertThat("source should sync with target", bean.simpleString, equalTo("newText"));

            bean.simpleString = "Adobe";
            assertThat("target should sync with the source", txt.text, equalTo("Adobe"));

            txt.text = null;
            assertThat("should should sync with null values from target", bean.simpleString, nullValue());

            autoBinding.unbind();
            bean.simpleString = "000";
            txt.text = "AAA";
            assertThat("target shouldn't sync after unbind()", txt.text, equalTo("AAA"));
            assertThat("source shouldn't sync after unbind()", bean.simpleString, equalTo("000"));
        }

        // negative tests
        [Test]
        public function testAutoBindingThrowsErrorWhenUpdateStrategyInNotDefined():void
        {
            assertThat( function():void
            {
                autoBinding = new AutoBinding(null,
                                            bean, BeanProperty.create("simpleString"),
                                            txt, BeanProperty.create("text"), "readOnceBinding");
            }, throws(instanceOf(ArgumentError)));
        }

        [Test]
        public function testWithTargetReadOnly():void
        {
            var listener:BindingListenerCounter = new BindingListenerCounter();

            bean.simpleString = "value01";
            autoBinding = new AutoBinding(UpdateStrategy.READ_WRITE,
                    bean, BeanProperty.create("simpleString"),
                    txt, BeanProperty.create("uuid"), "invalidBinding");

            autoBinding.addBindingListener( listener );

            autoBinding.bind();
            assertThat( "syncFailed should have been called", listener.syncFailedCounter, equalTo( 1 ) );
            assertThat( listener.syncedCounter, equalTo(0));
            assertThat( listener.boundCounter, equalTo(1));
            assertThat( listener.counter, equalTo(2));
            assertThat("wrong sync failure", listener.lastSyncFailure.getType(), equalTo(SyncFailureType.TARGET_UNWRITEABLE ));
        }

        [Ignore(reason="To be defined how to handle this corner case, where the dev sets a setter-only prop as source of binding")]
        [Test]
        public function testWithSourceWriteOnly():void
        {
            var listener:BindingListenerCounter = new BindingListenerCounter();
            autoBinding = new AutoBinding(UpdateStrategy.READ,
                                        bean, BeanProperty.create("setOnlyProp"),
                                        txt, BeanProperty.create("text"), "invalidBinding");
            autoBinding.addBindingListener(listener);

            autoBinding.bind();
            assertThat("syncFailed should have been called", listener.syncFailedCounter, equalTo(1));
            assertThat( listener.syncedCounter, equalTo(0));
            assertThat( listener.boundCounter, equalTo(1));
            assertThat(listener.counter, equalTo(2));
            assertThat("wrong sync failure", listener.lastSyncFailure.getType(), equalTo(SyncFailureType.SOURCE_UNREADABLE));

            assertThat("target value shouldn't change", txt.text, equalTo("someText"));
        }

    }
}

import mx.utils.UIDUtil;

class MockTextInput
{
    [Bindable]
    public var text:String = "someText";

    private var _uuid:String;

    public function MockTextInput():void
    {
        _uuid = UIDUtil.createUID();
    }

    public function get uuid():String
    {
        return _uuid;
    }

}
